import java.util.ArrayList;
import java.util.List;
import java.io.*;
import java.lang.Math;

/** Create and modify an intermediate version table.<br>
 * Rep: Each row in the table represents one line of code. Within each row are values:<br>
 * - source line - actual source line of code.<br>
 * - location counter - location counter at current instruction.<br>
 * - error list - all errors contained in line of code.<br>
 * - instruction code - 32 bit instruction code for current instruction.<br>
 * - operand symbol expression - the expression representing the operands for instruction.<br><Br>
 * Also as represented in the intermediate version table are the total length of the 
 * intermediate version table, the initial construction code for use in building each row, 
 * and the programs starting location in memory.<br><br>
 * 
 * Change Log<br>
 * - 04/19/09 - Tobin Chee<br>
 * - Added public methods to allow users to modify sections or the entire instruction code.<br>
 * - Added private methods to facilitate the conversion of decimal values into binary instruction code.<br>
 * - Added methods to IntermediateTable to facilitate parsing operands<br><br>
 * 
 * 	- 04/22/09 - Tobin Chee<br>
 * 		- Added a method IntermediateTable.putToFile() to allow us to see the contents of the intermediateVersion.<br><br>
 * 	- 04/24/09 - Tobin Chee<br>
 * 		- Implemented conversion algorithm to allow for negative two's complement address values in the intermediate file<br><br>
 * 	- 04/27/09 - Tobin Chee<br>
 * 		- Added debug bit function<br><br>
 * 	- 05/17/09 - Tobin Chee<br>
 * 		- Changed integer variables inside IntermediateTable.getInstructionBits() 
 * 		method to deal with boundary case issues. The object file should now be outputting
 * 		correct object code. With the exception of those handled by ben's method<br><br>
 *	- 05/26/09 0 Tobin Chee<br>
 *		- Added global variable containsFatalError to allow pass2 to easily determine whether to output the object file or not.
 *		- Added get and set methods for containsFatalError.
 * 
 * @author Tobin Chee
 */
public class IntermediateTable {
	/** ArrayList representation of source line from code.
	 */
	private List<String> intermediateSourceLine = new ArrayList<String>();
	/** ArrayList representation of location counter.
	 */
	private List<Integer> intermediateLocationCounter = new ArrayList<Integer>();
	/** ArrayList representation of error list.
	 */
	private List<List<Integer>> intermediateErrorList = new ArrayList<List<Integer>>();
	/** ArrayList representation of instruction code.
	 */
	private List<Integer[]> intermediateInstructionCode = new ArrayList<Integer[]>();
	/** ArrayList representation of operand symbol expression.
	 */
	private List<ArithmeticExpression> intermediateOperandSymbolExpression = new ArrayList<ArithmeticExpression>();
	/** int representation of length of intermediate version.
	 */
	private int length = 0;
	/** Array containing initial instruction code.
	 */
	private Integer[] initialInstructionCode = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
	/** int representation of program's starting location.
	 */
	private int programStartLocation = 0;
	/**
	 * List of boolean values signifying whether this line of code consumes memory or not.
	 */
	private List<Boolean> intermediateConsumesMemory = new ArrayList<Boolean>();
	/**
	 * Boolean value signifying whether a fatal error has ever been logged in the intermediate file.
	 */
	private boolean containsFatalError = false;
	
	/** Construct an intermediate table
	 */
	public IntermediateTable(){
	}
	
	
	/**
	 * Calculates and outputs the correct number of tabs based on the width of a formatted column (width in tabs)
	 * and the length of the string output in the column.
	 * 
	 * @param reportFile the file this method writes to.
	 * @param columnWidth the width of a formatted column in tabs.
	 * @param stringLength the length of the string output in the column.
	 */
	private void putTabsToFile(BufferedWriter out, Integer columnWidth, Integer stringLength) throws IOException{
		
		final int tabWidth = 6;
		int tabs = ((columnWidth * tabWidth) - stringLength) / tabWidth;
		if((stringLength % tabWidth) != 0){
			tabs++;
		}
		for(int j = 0; j < tabs; j++){
			out.write("\t");
		}
	}
	
	/**
	 * Determines if an address defined in the intermediate file.
	 * 
	 * @param addr address value to be tested.
	 * @return true, if addr is defined in IntermediateVersion.
	 * 
	 * Change Log:<br>
	 * 		- No Changes.
	 */
	public boolean containsLocationCounter(int addr){
		
		for(int i = 0; i < this.length; i++){
			
			if(this.getConsumesMemory(i)){
				
				if(this.getLocationCounter(i) == addr){
					return true;
				}
				//else do nothing
			}
		}
		return false;
	}
	
	/**
	 * Writes the contents of the intermediate table to file, formatted similarly to Appendix E in
	 * the CSE7000 lab manual.
	 * 
	 * @param out file to which this method writes.
	 * 
	 * Change Log:<br>
	 *  - No changes
	 */
	public void putToFile(BufferedWriter out, ErrorTable errors) throws IOException{
		String temp;
		//header
		out.write("ABS LOC\tOBJ CODE\tSTMT#\t\tSOURCE STMT\n");
		out.write("(HEX)\t\t(HEX)\t\t(DEC)\n");
		for(int i = 0; i < this.length; i++){
			//REL LOC (relative location)
			if(this.getLocationCounter(i) < 0){
				temp = "----";
			}
			else{ //if LOC is positive
				temp = Integer.toHexString(this.getLocationCounter(i));
			}
			
			while(temp.length() < 4){
				temp = "0" + temp;
			}
			out.write(temp);
			putTabsToFile(out, 2, temp.length());
			//OBJ CODE (object code)
			if(this.getConsumesMemory(i)){
				temp = Integer.toHexString(this.getInstructionCode(i));
				
				while(temp.length() < 8){
					temp = "0" + temp;
				}
			}
			else{
				temp = "--------";
			}

			out.write(temp);
			putTabsToFile(out, 2, temp.length());
			//STMT# (line# in the source file)
			temp = Integer.toHexString(i);
			out.write(temp);
			putTabsToFile(out, 2, temp.length());
			//SOURCE STMT (line of code from source file)
			temp = this.getSourceLine(i);
			out.write(temp + "\n");
			
			//ERRORS
			for(int errorCode: intermediateErrorList.get(i)){
				temp = "\terror:" + errors.getErrorSeverity(errorCode).toString() + ": " + errors.getErrorDescription(errorCode);
				out.write(temp + "\n");
			}
		}
	}
	
	/**
	 * Returns a boolean value signifying whether a fatal error has ever been logged in the intermediate file.
	 * 
	 * @return true, if a fatal error has been logged<br>
	 * false, if a fatal error has never been logged
	 */
	public boolean getContainsFatalError(){
		return containsFatalError;
	}
	
	/**
	 * Sets the value of the boolean flag signifying whether a fatal error has ever been logged in the intermediate file.
	 * 
	 * @param containsFatalErrors value to which the boolean flag containsFatalError will be set.
	 */
	public void setContainsFatalError(boolean hasFatalError){
		containsFatalError = hasFatalError;
	}
	
	/**
	 * Returns the character value signifying the ARC flag for the line of code at entryIndex<br><br>
	 * Requires: getConsumesMemory(entryIndex) == true
	 * 
	 * @param entryIndex The line of the source code on which the line of code appears.
	 * @return character representing the ARC flag of the instruction.
	 */
	public char getARCFlag(int entryIndex){
		ArithmeticExpression exp = this.getOperandSymbolExpression(entryIndex);
		Character arc;
		Character result = 'A';
		for(int i = 0; i < exp.length(); i++){
			arc = exp.getARCFlag(i);
			
			if(arc == 'C'){
				result = arc;
			}
			else if((arc == 'A') && (result == 'A')){
				//do nothing, the result is already 'A'
			}
			else if((arc == 'R') && (result != 'C')){
				result = arc;
			}
		}
		return result;
	}
	
	/**
	 * Sets the boolean value signifying whether the line of code at entryIndex consumes
	 * memory or not.
	 * 
	 * @param entryIndex
	 * @author Tobin Chee
	 */
	public void setConsumesMemory(int entryIndex, Boolean consumesMemory){
		intermediateConsumesMemory.set(entryIndex, consumesMemory);
	}
	
	/**
	 * Returns the boolean value signifying whether the line of code at entryIndex consumes
	 * memory or not.
	 * 
	 * @param entryIndex
	 * @return True if the code at entryIndex consumes memory. Otherwise false.
	 * @author Tobin Chee
	 */
	public Boolean getConsumesMemory(int entryIndex){
		return intermediateConsumesMemory.get(entryIndex);
	}
	
	/** Set the starting location of the program.
	 * @param location Location to start program.
	 */
	public void setProgramStartLocation(int location){
		programStartLocation = location;
	}
	
	/**
	 * Returns the starting location of the program.
	 * 
	 * @return integer line of code program execution starts at
	 */
	public int getProgramStartLocation(){
		return programStartLocation;
	}
	
	/**
	 * Sets instruction bits within a range to a specified value. Value is considered a normal binary number, not a
	 * two's comp integer.
	 * Requires: value > 0
	 *
	 * @param entryIndex Line in intermediate table to alter.
	 * @param value Value to set range to. 
	 * @param start Beginning of range.
	 * @param end End of range.
	 */
	private void setInstructionBits(int entryIndex, int value, int start, int end) {
		
		for(int i = start; i <= end; i++){
			double two = 2;
			double power = end - i;
			try{
				long result = (long)Math.pow(two, power);
				long temp = (long)value / result;
				intermediateInstructionCode.get(entryIndex)[i] = (int)((long)value / result);
				value = (int)((long)value % result);
			} catch (NumberFormatException nfe) {
				System.out.println("ASSEMBLER ERROR : IntermediateTable.setInstructionBits()");
			}
		}
	}
	
	/** Set instruction bits within a range to a specified value. Value is considered a two's comp integer.
	 * 
	 * @param entryIndex Line in intermediate table to alter.
	 * @param value Value to set range to. 
	 * @param start Beginning of range.
	 * @param end End of range.
	 */
	private void setInstructionBitsTwosComp(int entryIndex, int value, int start, int end) {
		
		if(value >= 0){ //if positive
			setInstructionBits(entryIndex, value, start, end);
		}
		else{ //if value is negative
			Boolean firstOneDetected = false;
			long valTemp = Math.abs((long)value);
			
			for(int i = start; i <= end; i++){
				double two = 2;
				double power = end - i;
				
				try{
					double result = Math.pow(two, power);
					intermediateInstructionCode.get(entryIndex)[i] = (int)(valTemp / (long)result);
					valTemp = (valTemp % (long)result);
				} catch (NumberFormatException nfe) {
					System.out.println("ASSEMBLER ERROR : IntermediateTable.setInstructionBitsTwosComp()");
				}
			}
			
			for(int i = end; i >= start; i--){
				
				if(firstOneDetected){ //if first one detected, flip bits
					intermediateInstructionCode.get(entryIndex)[i] = Math.abs(intermediateInstructionCode.get(entryIndex)[i] - 1);
				}
				else if(intermediateInstructionCode.get(entryIndex)[i] == 1){
					firstOneDetected = true;
				}
			}
		}
	}
	
	/** Returns the value of the designated bits. Bits are considered to be encoded
	 * as unsigned binary.
	 * 
	 * @param entryIndex
	 * @param start
	 * @param end
	 * @return
	 */
	private int getInstructionBits(int entryIndex, int start, int end){
		int value = 0;
		for(int i = start; i <= end; i++){
			int result = (int)(Math.pow(2, end - i));
			value += intermediateInstructionCode.get(entryIndex)[i] * result;
		}
		return value;
	}
	
	/** Returns the value of the designated bits. Bits are considered to be encoded
	 * as two's complement.
	 * 
	 * @param entryIndex Location in intermediate table to access.
	 * @param start Range to start.
	 * @param end Range to end.
	 * @return Value of range of bits
	 * 
	 * Change Log:
	 * 	- 05/17/09 - Tobin Chee
	 * 		- Changed value and result to long and double respectively, to deal with boundary case issues.
	 */
	private int getInstructionBitsTwosComp(int entryIndex, int start, int end){
		long value = 0;
		
		if(intermediateInstructionCode.get(entryIndex)[start] == 1){ //if negative
			Boolean firstOneDetected = false;
			
			for(int i = end; i >= start; i--){
				double result = (Math.pow(2, end - i));
				
				if(firstOneDetected){
					value -= Math.abs(intermediateInstructionCode.get(entryIndex)[i] - 1) * (long)result;
				}
				else{
					
					if(intermediateInstructionCode.get(entryIndex)[i] == 1){
						firstOneDetected = true;
					}
					value -= intermediateInstructionCode.get(entryIndex)[i] * (long)result;
				}
			}
		}
		else{ //if positive
			value = getInstructionBits(entryIndex, start, end);
		}
		return (int)value;
	}
	
	/** Add a new entry line to intermediate table.
	 */
	public void addEntry(){
		intermediateSourceLine.add("");
		intermediateLocationCounter.add(-1);
		intermediateErrorList.add(new ArrayList<Integer>());
		intermediateInstructionCode.add(initialInstructionCode.clone());
		intermediateOperandSymbolExpression.add(new ArithmeticExpression(-1, ArithmeticExpression.Sign.POSITIVE));//Arbitrary default value
		intermediateConsumesMemory.add(false); //Arbitrary default value
		length++;
	}
	
	/** Returns number of entries in the intermediate table.
	 * @return Length of intermediate table.
	 */
	public int length(){
		return length;
	}
	
	/** Update the address field of an instruction.
	 * @param entryIndex Location to update.
	 * @param addr Address to update.
	 */
	public void updateAddr(int entryIndex, int addr){
		this.setInstructionBitsTwosComp(entryIndex, addr, 16, 31);
	}
	
	/** Find the address of an instruction.
	 * @param entryIndex Location to search.
	 * @return Address of instruction. 
	 */
	public int getAddr(int entryIndex){
		return this.getInstructionBitsTwosComp(entryIndex, 16, 31);
	}
	
	/** Update the address flag of an instruction.
	 * @param entryIndex Location to update.
	 * @param addrFlag Flag to be updated instruction.
	 */
	public void updateAddrFlag(int entryIndex, int addrFlag){
		this.setInstructionBits(entryIndex, addrFlag, 14, 15);
	}
	
	/** Find the address flag of an instruction.
	 * @param entryIndex Location to search.
	 * @return Address flag of instruction.
	 */
	public int getAddrFlag(int entryIndex){
		return this.getInstructionBitsTwosComp(entryIndex, 14, 15);
	}
	
	/** Update the IOWD flag of an instruction.
	 * @param entryIndex Location to update.
	 * @param IOWDFlag Flag of instruction.
	 */
	public void updateIOWDFlag(int entryIndex, int IOWDFlag){
		this.setInstructionBits(entryIndex, IOWDFlag, 12, 13);
	}
	
	/** Find the IOWD flag of an instruction.
	 * @param entryIndex Location to search.
	 * @return IOWD flag of instruction.
	 */
	public int getIOWDFlag(int entryIndex){
		return this.getInstructionBitsTwosComp(entryIndex, 12, 13);
	}
	
	/** Sets the default instruction value with the debug bit on,
	 *  so that new entries always receive this debug bit.
	 * @param debug True if debug bit is to be turned on.
	 */
	public void setDebugFlag(Boolean debug){
		if(debug){
			initialInstructionCode[11] = 1;
		}
		else{ //if !debug
			initialInstructionCode[11] = 0;
		}
	}
	
	/** Update debug bit of an instruction.
	 * @param entryIndex Location to update.
	 * @param debugBit Debug bit to update instruction.
	 */
	public void updateDebugBit(int entryIndex, int debugBit){
		this.setInstructionBits(entryIndex, debugBit, 11, 11);
	}
	
	/** Find the debug bit of an instruction.
	 * @param entryIndex Location to search.
	 * @return Debug bit of instruction.
	 */
	public int getDebugBit(int entryIndex){
		return this.getInstructionBitsTwosComp(entryIndex, 11, 11);
	}
	
	/** Update the index register of an instruction.
	 * @param entryIndex Location to update.
	 * @param indexRegister Index register of instruction.
	 */
	public void updateIndexRegister(int entryIndex, int indexRegister){
		this.setInstructionBits(entryIndex, indexRegister, 8, 10);
	}
	
	/** Find the index register of an instruction.
	 * @param entryIndex Location to search.
	 * @return Index register of instruction.
	 */
	public int getIndexRegister(int entryIndex){
		return this.getInstructionBitsTwosComp(entryIndex, 8, 10);
	}
	
	/** Update the opcode of an instruction.
	 * @param entryIndex Location to update.
	 * @param opCode Opcode of instruction.
	 */
	public void updateOpCode(int entryIndex, int opCode){
		this.setInstructionBits(entryIndex, opCode, 0, 7);
	}
	
	/** Returns the opcode of the instruction at designated index.
	 * @param entryIndex Location to search.
	 * @return Opcode of instruction. 
	 */
	public int getOpCode(int entryIndex){
		return this.getInstructionBitsTwosComp(entryIndex, 0, 7);
	}
	
	/** Update the operand symbol expression.
	 * @param entryIndex Location to update.
	 * @param exp ArithmeticExpression to update symbol.
	 */
	public void updateOperandSymbolExpression(int entryIndex, ArithmeticExpression exp){
		intermediateOperandSymbolExpression.set(entryIndex, exp);
	}
	
	/** Find the operand symbol expression of an instruction.
	 * @param entryIndex Location to search.
	 * @return Operand symbol expression of instruction.
	 */
	public ArithmeticExpression getOperandSymbolExpression(int entryIndex){
		return intermediateOperandSymbolExpression.get(entryIndex);
	}
	
	/** Update entire instruction code of an instruction.
	 * @param entryIndex Location to update.
	 * @param instCode instruction code of instruction.
	 */
	public void updateInstructionCode(int entryIndex, int instCode){
		this.setInstructionBitsTwosComp(entryIndex, instCode, 0, 31);
	}
	
	/** Find the instruction code of an instruction.
	 * @param entryIndex Location to search.
	 * @return Instruction code of instruction.
	 */
	public int getInstructionCode(int entryIndex){
		return this.getInstructionBitsTwosComp(entryIndex, 0, 31);
	}
	
	/** Update the source line of an instruction.
	 * @param entryIndex Location to update.
	 * @param sourceLine Source line of instruction.
	 */
	public void updateSourceLine(int entryIndex, String sourceLine){
		intermediateSourceLine.set(entryIndex, sourceLine);
	}
	
	/** Find the source line of an instruction.
	 * @param entryIndex Location to search.
	 * @return Source line of instruction.
	 */
	public String getSourceLine(int entryIndex){
		return intermediateSourceLine.get(entryIndex);
	}
	
	/** Add an error to the instruction at the given index.
	 * @param entryIndex Location to add error.
	 * @param errorCode Error to ad to instruction.
	 */
	public void addError(int entryIndex, int errorCode){
		intermediateErrorList.get(entryIndex).add(errorCode);
	}
	
	/** Find error in instruction.
	 * @param entryIndex Location to search.
	 * @return Error in instruction
	 */
	public List<Integer> getErrors(int entryIndex){
		return intermediateErrorList.get(entryIndex);
	}
	
	/** Find the number of errors in an instruction.
	 * @param entryIndex Location to search.
	 * @return Number of errors.
	 */
	public int numberOfErrors(int entryIndex){
		return intermediateErrorList.size();
	}
	
	/** Update the location counter for an instruction.
	 * @param entryIndex Location to update.
	 * @param locationCounter Location counter of instruction.
	 */
	public void updateLocationCounter(int entryIndex, int locationCounter){
		intermediateLocationCounter.set(entryIndex, locationCounter);
	}
	
	/** Find the location counter for an instruction.
	 * @param entryIndex Location to search.
	 * @return Location counter at instruction.
	 */
	public int getLocationCounter(int entryIndex){
		return intermediateLocationCounter.get(entryIndex);
	}
}
